//
//  objc.m
//  iCodeMac
//
//  Created by Andrew Barry on 9/10/09.
//  Copyright 2009 Barry Computer Services. All rights reserved.
//

#import "objc.h"
#import "objc/runtime.h"

static jsvalue objCWrapperConstructor(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	return JSVAL_VOID;
}

static jsvalue objCMethodWrapperConstructor(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	return JSVAL_VOID;
}

static jsvalue objCCustomGet(jscontext *cx, jsvalue me, int h, jsvalue key) {
	NSString *field = jsasstrRetain(cx, key);
	NSObject *obj = (NSObject *) (jsgetslot(cx, me, 0, "_obj") & ~1);
	jsvalue rv = JSVAL_VOID;

	// a method reference
	SEL selector = sel_registerName([field UTF8String]);
	if ([obj respondsToSelector:selector]) {
		NSMethodSignature *sig = [obj methodSignatureForSelector:selector];
		[sig retain];
		
		jsvalue System = cx->slots[GLOBAL_SYSTEM];
		
		jsvalue wrapper = jsnew(cx, jsgetslot(cx, System, 0, "_objcm"), nil, 0);
		jssetslot(cx, wrapper, 0, "_sig", ((int) sig) | 1);
		jssetslot(cx, wrapper, 0, "_sel", ((int) selector) | 1);
		return wrapper;
	}

	[field release];
	return rv;
}

static NSObject *jsasnsobject(jscontext *cx, jsvalue v) {
	if (JSVAL_IS_VOID(v) || JSVAL_IS_NULL(v)) {
		return 0;
	} else if (JSVAL_IS_INT(v)) {
		return [NSNumber numberWithInt:JSVAL_TO_INT(v)];
	} else if (JSVAL_IS_DOUBLE(v)) {
		return [NSNumber numberWithDouble:jsasnum(cx, v)];
	} else if (JSVAL_IS_STRING(v)) {
		return [jsasstrRetain(cx, v) autorelease];
	} else if (JSVAL_IS_OBJECT(v)) {
		return (NSObject *) (jsgetslot(cx, v, 0, "_obj") & ~1);
	} else {
		return 0;
	}
}

int invokeMsgSendV(int *buf, int count, int delta);

static jsvalue objCMethodCustomInvoke(jscontext *cx, jsvalue me, jsvalue thisArg, jsvalue *params, int paramCount) {
	NSMethodSignature *sig = (NSMethodSignature *) (jsgetslot(cx, me, 0, "_sig") & ~1);
	SEL sel = (SEL) (jsgetslot(cx, me, 0, "_sel") & ~1);
	NSObject *obj = (NSObject *) (jsgetslot(cx, thisArg, 0, "_obj") & ~1);

	if (!obj)
		return JSVAL_VOID;
	
	int *stackFrame = (int *) malloc(256);
	int pos = 0;
	
	stackFrame[pos++] = (int) obj;
	stackFrame[pos++] = (int) sel;
	
	int argCount = [sig numberOfArguments];

	for (int idx = 0; idx < paramCount; idx++) {
		jsvalue v = params[idx];
		if ((idx + 2) < argCount) {
			const char *pt = [sig getArgumentTypeAtIndex: idx+2];
			if (!strcmp(pt, "i") || !strcmp(pt, "I") || !strcmp(pt, "c")) {
				stackFrame[pos++] = jsasint(cx, v);
			} else if (!strcmp(pt, "@")) {
				stackFrame[pos++] = (int) jsasnsobject(cx, v);
			} else if (!strncmp(pt, "{_NSRect=", 9)) {
				if (isArray(v)) {
					for (int j = 0; j < 4; j++) {
						if (j < jsarraylength(v)) {
							*(float *) &stackFrame[pos++] = jsasnum(cx, jsarrayelem(v, j));
						} else {
							stackFrame[pos++] = 0;
						}
					}
				} else {
					NSLog(@"Bridge type mismatch");
					return JSVAL_VOID;
				}
			} else {
				NSLog(@"Unsupported type parameter");
				return JSVAL_VOID;
			}
		} else {
			stackFrame[pos++] = (int) jsasnsobject(cx, v);
		}
	}

	int rv = invokeMsgSendV(stackFrame, pos, ((4 - (argCount & 3)) & 3) * 4 );
	free(stackFrame);
	
	const char *returnType = [sig methodReturnType];
	if (!strcmp(returnType, "@")) {
		id robj = (id) rv;
		if (!robj)
			return JSVAL_VOID;
		if ([robj isKindOfClass:[NSString class]]) {
			return jsstrFromNSString(cx, (NSString *) robj);
		} else if ([robj isKindOfClass:[NSNumber class]]) {
			return jsnum(cx, [robj doubleValue]);
		} else {
			return wrapCocoaObject(cx, robj);
		}
	} else if (!strcmp(returnType, "i")) {
		return jsnumi(cx, rv);
	}
	return JSVAL_VOID;
}

static jsvalue objcLookupClass(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc == 0)
		return JSVAL_VOID;
	NSString *clsName = jsasstrRetain(cx, args[0]);
	id cls = objc_lookUpClass([clsName UTF8String]);
	[clsName release];
	if (cls)
		return wrapCocoaObject(cx, cls);
	else
		return JSVAL_VOID;

}

jsvalue wrapCocoaObject(jscontext *cx, id obj) {
	jsvalue System = cx->slots[GLOBAL_SYSTEM];

	jsvalue wrapper = jsnew(cx, jsgetslot(cx, System, 0, "_objc"), nil, 0);
	jssetslot(cx, wrapper, 0, "_obj", ((int) obj) | 1);
	return wrapper;
}

void registerObjC(jscontext *cx) {
	jsvalue objCWrapperClass;
	jsvalue System = cx->slots[GLOBAL_SYSTEM];
	
	objCWrapperClass = bindFunction(cx, objCWrapperConstructor);
	jsvalue objCMethodWrapperClass = bindFunction(cx, objCMethodWrapperConstructor);

	jssetslot(cx, System, 0, "_objc", objCWrapperClass);
	jssetslot(cx, System, 0, "_objcm", objCMethodWrapperClass);
	jssetslot(cx, System, 0, "objcClass", bindFunction(cx, objcLookupClass));
	
	((jsfunction *) objCWrapperClass)->customGet = objCCustomGet;
	
	((jsfunction *) objCMethodWrapperClass)->customInvoke = objCMethodCustomInvoke;
}
